package com.wujw.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Desc:
 *
 * @author wujw
 * @email jiwenwu@outlook.com
 * @date 2018/11/18
 */
public class NIOServer implements Runnable {
    // 缓冲区大小
    private final int BUFFER_SIZE = 1024;
    // 监听的端口
    private final int PORT = 8888;
    //  多路复用器，负责管理通道
    private Selector selector;
    // 缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);

    /**
     * NIO 也叫New IO / Non-Block IO ,非阻塞同步通信方式
     * 从BIO的阻塞到NIO的非阻塞，这是一大进步，归功于Buffer,Channel,Selector三个设计实现
     * Buffer : 缓冲区，NIO通过的数据操作都是在缓冲去进行，缓冲区实际上是一个数组，而BIO是将数据直接写入或读取到Stream对象
     * Channel : 通道，NIO可以通过Channel进行数据的读，写同时读写操作
     * Selector :多路复用，NIO编程的基础，多路复用骑提供选择已经就绪状态任务的能力。
     * 客户端和服务器通过Channel连接，而这些Channel都要注册Selector。Selector 通过一个线程不停的轮询这些Channel，找出已经
     * 准备就绪的Channel执行IO操作。
     * NIO通过一个线程轮询，实现千万个客户端的请求，这就是非阻塞NIO的特点。
     */

    public NIOServer(){
        startServer();
    }


    private void startServer() {
        try {
            // 1.开启多路复用器
            selector = Selector.open();
            // 2.打开服务器通道（网络读写通道）
            ServerSocketChannel channel = ServerSocketChannel.open();
            // 3.设置服务器通道为非阻塞模式，true为阻塞
            channel.configureBlocking(false);
            // 4.绑定端口
            channel.socket().bind(new InetSocketAddress(PORT));
            // 5.把通道注册到多路复用器上，并监听阻塞时间
            /**
             * OP_READ  关注读数据就绪事件
             * OP_WRITE  关注写数据就绪事件
             * OP_CONNECT 关注
             * OP_ACCEPT
             */
            channel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("server start >>>>> port: " + PORT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 启动线程负责Selector的轮询
     */
    @Override
    public void run() {
        while (true) {
            try {
                // 1.多路复用器监听阻塞
                selector.select();
                // 2.多路复用器已经选择的结果集
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                // 3.不停的轮询
                while (iterator.hasNext()) {
                    // 4.获取一个选中的key
                    SelectionKey key = iterator.next();
                    // 5.获取后便将其从容器的移除
                    iterator.remove();
                    // 6.只获取有效的key
                    if (!key.isValid()) {
                        continue;
                    }
                    // 7.阻塞状态的处理
                    if (key.isAcceptable()) {
                        accept(key);
                    }
                    // 8.可读状态的处理
                    if (key.isReadable()) {
                        read(key);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 可读状态的处理
     *
     * @param key
     */
    private void read(SelectionKey key) {
        try {
            // 1.清空缓冲区的数据
            readBuffer.clear();
            // 2.获取多路复用器上注册的通道
            SocketChannel socketChannel = (SocketChannel) key.channel();
            // 3.读取数据返回
            int count = socketChannel.read(readBuffer);
            // 代表无数据
            if (count == -1) {
                key.channel().close();
                key.cancel();
                return;
            }
            // 4.有数据则在读取数据前进行复位操作
            readBuffer.flip();
            // 5.根据缓冲区大小创建一个相应大小的byte数组，用来获取值
            byte[] bytes = new byte[readBuffer.remaining()];
            // 6.接收缓冲区数据
            readBuffer.get(bytes);
            // 7.打印数据
            System.out.println("NIO server : " + new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 阻塞状态的处理
     *
     * @param key
     */
    private void accept(SelectionKey key) {
        try {
            // 1.获取通道服务
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 2.执行阻塞方法
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 3.设置服务其为非阻塞模式，
            serverSocketChannel.configureBlocking(false);
            // 4.把通道注册到多路复用器上，并设置读取标识
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(new NIOServer()).start();
    }

}
